package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SpuDTO;
import com.leyou.item.entity.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.SearchRepository;
import com.leyou.search.utils.HighlightUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private SearchRepository searchRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private ItemClient itemClient;

    /**
     * 将item服务提供的SpuDTO对象转成search服务索引库中的Goods对象
     */
    public Goods buildGoods(SpuDTO spuDTO){
        //第三步：根据SpuId查询Sku集合 封装skus和price字段
        //根据SpuId查询Sku集合
        List<Sku> skus = itemClient.findSkusBySpuId(spuDTO.getId());
        //提供map的list集合来存储索引库中sku需要的属性
        List<Map<String, Object>> skuList = new ArrayList<>();
        //遍历skus给skuList赋值
        skus.forEach(sku -> {
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            skuMap.put("price", sku.getPrice());
            skuMap.put("title", sku.getTitle().substring(spuDTO.getName().length()));//只需要除去SpuName之外的部分
            skuList.add(skuMap);
        });
        //封装得到所有sku的价格集合
        Set<Long> prices = skus.stream().map(Sku::getPrice).collect(Collectors.toSet());

        //第四步：封装all字段
        String all = spuDTO.getBrandName()
                + spuDTO.getCategoryName()
                + skus.stream().map(Sku::getTitle).collect(Collectors.joining());

        //创建出存储动态过滤条件的map
        Map<String, Object> specFilterMap = new HashMap<>();
        //第五步：根据第三级分类和searching等于true查询需要过滤的规格参数 得到Specs的key
        List<SpecParam> specParams = itemClient.findSpecParams(null, spuDTO.getCid3(), true);
        //第六步：根据SpuId查询SpuDetail对象 得到得到Specs的value
        SpuDetail spuDetail = itemClient.findSpuDetailBySpuId(spuDTO.getId());
        //得到通用规格参数所在字段
        String genericSpecStr = spuDetail.getGenericSpec();
        //将字符串类型的通用规格参数转成map
        Map<Long, Object> genericSpecObj = JsonUtils.toMap(genericSpecStr, Long.class, Object.class);
        //得到特有规格参数所在字段
        String specialSpecStr = spuDetail.getSpecialSpec();
        //将字符串类型的特有规格参数转成map
        Map<Long, List<Object>> specialSpecObj = JsonUtils.nativeRead(specialSpecStr, new TypeReference<Map<Long, List<Object>>>() {});
        //遍历specParams给specFilterMap赋值
        specParams.forEach(specParam -> {
            //指定specFilterMap的key值
            String key = specParam.getName();
            //指定specFilterMap的value值
            Object value = null;
            //通过判断当前规格参数是否是通用规格参数来确定value的来源
            if(specParam.getGeneric()){
                //通过规格参数
                value = genericSpecObj.get(specParam.getId());
            }else {
                //特有规格参数
                value = specialSpecObj.get(specParam.getId());
            }
            //对所有数字类型的动态过滤条件做处理，将其兑换成区间
            if(specParam.getNumeric()){
                value = chooseSegment(value, specParam);
            }
            specFilterMap.put(key, value);
        });

        //第一步：创建Goods对象
        Goods goods = new Goods();
        //第二步：先给Goods对象中可以使用SpuDTO参数直接赋值的属性赋值
        goods.setId(spuDTO.getId());
        goods.setSpuName(spuDTO.getName());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setSkus(JsonUtils.toString(skuList));//通过第三步赋值
        goods.setPrice(prices);//通过第三步赋值
        goods.setAll(all);//通过第四步赋值
        goods.setSpecs(specFilterMap);//通过第五，六步赋值
        return goods;
    }

    /**
     * 将数字类型的规格参数兑换成区间
     * @param value 数字类型的规格参数的值
     * @param p 当前规格参数对象
     * @return
     */
    private String chooseSegment(Object value, SpecParam p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 分页查询商品对象业务逻辑
     * @param request
     * @return
     */
    public PageResult<GoodsDTO> goodsPageQuery(SearchRequest request) {
        //创建一个封装复杂条件的对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装分页条件
        nativeSearchQueryBuilder.withPageable(PageRequest.of(request.getPage()-1, request.getSize()));
        //封装查询的字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuName", "skus", "subTitle"}, null));
        //封装查询条件
        nativeSearchQueryBuilder.withQuery(addSearchKey(request));
        //设置高亮参数
        HighlightUtils.highlightField(nativeSearchQueryBuilder, "spuName");
        //索引库查询
        AggregatedPage<Goods> pageResult = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(),
                Goods.class, HighlightUtils.highlightBody(Goods.class, "spuName"));
        //获取查询的数据列表
        List<Goods> goodsList = pageResult.getContent();
        if(CollectionUtils.isEmpty(goodsList)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //封装自定义的分页对象
        PageResult<GoodsDTO> result = new PageResult<>(
                pageResult.getTotalElements(),
                pageResult.getTotalPages(),
                BeanHelper.copyWithCollection(goodsList, GoodsDTO.class)
        );
        //封装自定义的分页对象
        return result;
    }

    /**
     * 封装查询条件
     * @param request
     * @return
     */
    private QueryBuilder addSearchKey(SearchRequest request) {
        //初始化一个Bool查询对象
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //添加搜索条件
        queryBuilder.must(QueryBuilders.multiMatchQuery(request.getKey(), "spuName", "all").operator(Operator.AND));
        //获取所有的过滤条件map
        Map<String, Object> filterParams = request.getFilterParams();
        if(!CollectionUtils.isEmpty(filterParams)){
            //遍历filterParams
            filterParams.entrySet().forEach(entry->{
                //获取key值
                String key = entry.getKey();
                //对key值进行处理
                if(StringUtils.equals(key, "分类")){
                    key = "categoryId";
                }
                if(!StringUtils.equals(key, "分类") && !StringUtils.equals(key, "brandId")){
                    key = "specs."+key+".keyword";
                }
                //获取value值
                Object value = entry.getValue();
                queryBuilder.filter(QueryBuilders.termQuery(key, value));
            });
        }
        return queryBuilder;
    }

    /**
     * 查询过滤条件数据
     * @param request
     * @return
     */
    public Map<String, List<?>> queryFilterParams(SearchRequest request) {
        Map<String, List<?>> resultMap = new LinkedHashMap<>();
        //封装一个复杂条件查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //封装查询条件
        nativeSearchQueryBuilder.withQuery(addSearchKey(request));
        //封装查询的字段【可以不写】
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuName", "skus", "subTitle"}, null));
        //封装分页条件【可以不写】
        nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));
        //指定分类聚合的名称
        String categoryAgg = "categoryAgg";
        //添加分类聚合条件  如果要查询全部聚合结果，需要在聚合条件后面加.size(Integer.MAX_VALUE)
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //指定品牌聚合的名称
        String brandAgg = "brandAgg";
        //添加品牌聚合条件  如果要查询全部聚合结果，需要在聚合条件后面加.size(Integer.MAX_VALUE)
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        //聚合查询
        AggregatedPage<Goods> aggResult = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        //获取所有聚合结果
        Aggregations aggregations = aggResult.getAggregations();
        //通过分类聚合名称获取的分类聚合结果
        Terms categoryTerm = aggregations.get(categoryAgg);
        //将分类聚合结果封装到resultMap中
        List<Long> cids = handlerCategoryAgg(resultMap, categoryTerm);
        //通过品牌聚合名称获取的分类聚合结果
        Terms brandTerm = aggregations.get(brandAgg);
        //将分类聚合结果封装到resultMap中
        handlerBrandAgg(resultMap, brandTerm);
        //封装动态过滤条件参数
        handlerSpecParamFilter(resultMap, cids, addSearchKey(request));
        return resultMap;
    }

    /**
     * 封装动态过滤条件
     * @param resultMap
     * @param cids
     * @param addSearchKey
     */
    private void handlerSpecParamFilter(Map<String, List<?>> resultMap, List<Long> cids, QueryBuilder addSearchKey) {
        cids.forEach(cid->{
            //封装一个复杂条件查询对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            //封装查询条件
            nativeSearchQueryBuilder.withQuery(addSearchKey);
            //封装查询的字段【可以不写】
            nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "spuName", "skus", "subTitle"}, null));
            //封装分页条件【可以不写】
            nativeSearchQueryBuilder.withPageable(PageRequest.of(0, 1));

            //根据当前分类id查询分类下所有可以被搜索的规格参数列表
            List<SpecParam> specParams = itemClient.findSpecParams(null, cid, true);
            //遍历规格参数列表
            specParams.forEach(specParam -> {
                //指定当前规格参数聚合的名称
                String aggName = specParam.getName();
                String fieldName = "specs."+specParam.getName()+".keyword";
                //添加当前规格参数聚合条件  如果要查询全部聚合结果，需要在聚合条件后面加.size(Integer.MAX_VALUE)
                nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(fieldName));
            });

            //聚合查询
            AggregatedPage<Goods> aggResult = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
            //获取所有聚合结果
            Aggregations aggregations = aggResult.getAggregations();

            specParams.forEach(specParam -> {
                //指定当前规格参数聚合的名称
                String aggName = specParam.getName();
                //通过名称获取聚合结果
                Terms terms = aggregations.get(aggName);
                List<String> aggList = terms.getBuckets()
                        .stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                //将聚合结构放入结果map中
                resultMap.put(aggName, aggList);
            });

        });

    }

    //将分类聚合结果封装到resultMap中
    private void handlerBrandAgg(Map<String, List<?>> resultMap, Terms brandTerm) {
        List<Long> brandIds = brandTerm.getBuckets()//获取品牌下所有的桶集合
                .stream()//转流
                .map(Terms.Bucket::getKeyAsNumber)//获取每一个Number类型的key值
                .map(Number::longValue)//将每一个Number类型的key值转成Long
                .collect(Collectors.toList());
        //通过品牌id的集合批量查询品牌对象集合
        List<Brand> brands = itemClient.findBrandsByIds(brandIds);
        resultMap.put("品牌", brands);
    }

    //将分类聚合结果封装到resultMap中
    private List<Long> handlerCategoryAgg(Map<String, List<?>> resultMap, Terms categoryTerm) {
        List<Long> categoryIds = categoryTerm.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //通过分类id的集合批量查询分类对象集合
        List<Category> list = itemClient.findCategoriesByIds(categoryIds);
        resultMap.put("分类", list);
        return categoryIds;
    }

    /**
     * 添加索引库
     * @param id
     */
    public void addIndex(Long id) {
        SpuDTO spuDTO = itemClient.findSpuDTObySpuId(id);
        Goods goods = buildGoods(spuDTO);
        searchRepository.save(goods);
    }

    /**
     * 删除索引库
     * @param id
     */
    public void delIndex(Long id) {
        searchRepository.deleteById(id);
    }
}
